// string number boolean null undefined symbol bigint object
let str = "hello";

// any 类型
let a: any = "aaa";
let aa = null;

// 字面量类型
const b = "hello";
const c = null;

let d: "world";
d = "world"
// d = "aaa"; // 报错

// 联合类型
let v1: string | number | boolean | undefined = undefined;
v1 = "aaa";
v1 = 123;
v1 = true
// v1 = null; // 报错

let v2: "男" | "女";
v2 = "男";

let v3: "UP" | "DOWN" | "LEFT" | "RIGHT" = "UP";
v3 = "LEFT";
v3 = "RIGHT";
console.log(v3)

// 数组类型
// 数组类型可以有两种表示方式
// 类型[]
// Array<类型>
const arr1: string[] = ["a", "b", "c"];
const arr2 = [1, 2, 3, 4, 5];
const arr3: Array<number> = [1, 2, 3, 4, 5];
const arr4: Array<string> = ["a", "b", "c"];

// 空数组赋值，在strict严格检查下，默认是any类型
// 但是这个和其他类型检查配置相关noImplicitAny
const arr5 = [];
arr5.push(1);
arr5.push("abc");

// 数组也能用联合类型
const arr6: (string | number)[] = ["a", 1, "b", 2];
const arr7: Array<string | number> = ["a", 1, "b", 2];
// 注意和下面的写法进行区分
const arr8: string[] | number[] = [1,2,3,4];
const arr9: Array<string> | Array<number> = ["a", "b", "c"];

// 元组类型
const tuple1: [string, number] = ["a", 1];
const tuple2: [number, number, number] = [1, 2, 3];

// 使用场景：坐标
// let position:number[] = [39.5436, 117.2312];
let position: [number, number] = [39.5436, 117.2312];
console.log(position)
// 一个容易混淆的赋值情况
let value = [];
let tuple3: [] = []; // 空元祖

function add(a:string, b:string):string { 
  return a + b;
}

const r = add("a", "b");

// 可选参数
// 可以在某些参数后面加上问号?,表示参数可以不用一定要传递
// 注意：可选参数必须要在必选参数后面
function sum1(a: number, b: number, c?:number) { 
  console.log(a, b, c);
}
sum1(1, 2);

// 默认参数本身就是可选参数
function sum2(a: number, b: number, c = 10) {
  console.log(a, b, c);
}
sum2(1, 2);

// 剩余参数
const fn = (a: number, b: number, ...rest: number[]) => { 
  console.log("----",a,b,rest);
}
fn(1, 2, 3, 4, 5, 6);

// 如果函数没有返回值类型，默认返回类型是void

// 泛型
function identity<T>(arg: T):T { 
  return arg
}

let output1 = identity<string>("myString");
// 也可以使用类型推断
let output2 = identity("myString");
let output3 = identity(123);
console.log(output1, output2, output3);

function getTuple1<T>(a:T, b:T):[T,T] { 
  return [a, b];
}
const as1 = getTuple1<string>("hello", "world");
console.log(as1);

function getTuple2<T, K>(a:T, b:K):[T,K] { 
  return [a, b];
}
const as2 = getTuple2<string,number>("hello", 123);
console.log(as2);

const as3 = getTuple2("hello", 123);
console.log(as3);

function myNumbersFilter(arr:number[], callback:(item:number, index?:number)=>boolean):number[] { 
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    if (callback(item, i)) { 
      result.push(item);
    }
  }
  return result;
}

const filterArr = myNumbersFilter([1, 2, 3, 4, 5], (item) => { 
  return item % 2 === 0;
})
console.log(filterArr);

// 使用泛型参数定义filter函数
function myFilter<T>(arr:T[], callback:(item:T, index?:number)=>boolean):T[] { 
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    if (callback(item, i)) { 
      result.push(item);
    }
  }
  return result;
}

const filterArr2 = myFilter(["xxx.js", "aaa.java", "bbb.ts", "ccc.ts"], item => item.endsWith(".ts"));
console.log(filterArr2);

// 对象字面量类型
const v4 = "hello";
const obj1 = {
  id: 1,
  name: "jack",
  age: 20
}

const obj2: {
  id: number,
  name: string,
  age: number,
  sex:"男" | "女"
} = {
  id: 1,
  name: "jack",
  age: 20,
  sex: "男"
}

const arr10: {
  name: string,
  age: number
}[] = [
  {
    name: "jack",
    age: 20
  },
  {
    name: "tom",
    age: 21
  }
]

function getInfo(user: { name: string, age: number }): { name: string, age: number }[] { 
  // todos...
  return [
    {
      name: "jack",
      age: 20
    },
    {
      name: "tom",
      age: 21
    }
  ]
}

// let user = { name: "lily", age: 21, sex: "女" };

getInfo({ name: "lily", age: 21 })


// 自定义类型：
// 两种定义类型的方式
// 类型别名：创建一个类型的新的名字，类型别名可以是任何有效的类型
// 接口：接口其实是面向对象的概念，所以一般用于定义对象类型

// 类型别名
// type 类型别名 = /*类型*/;
type Pointer = {
  x: number,
  y: number
}

type ID = number | string;

type Age = number;

type User = {
  id: ID,
  name: string,
  age: Age
}

const obj3: User = {
  id: 1,
  name: "jack",
  age: 20
}

// 接口
// interface 接口名 { /*属性名:属性类型*/ }
interface Point {
  x: number,
  y: number
}
interface Person {
  id: ID,
  name: string,
  age: Age
}
const obj4: Person = {
  id: 1,
  name: "jack",
  age: 20
}

// 在函数和数组中使用就非常方便了
function fn1(user: User) {
  console.log(user.name);
}

const persons: Person[] = [
  {
    id: 1,
    name: "rose",
    age: 20
  },
  {
    id: 2,
    name: "jack",
    age: 19
  }
]

type InfoFn = (id: number, name?: string) => string;

// 可选属性和函数声明
interface Book{
  id: number,
  name: string,
  price?: number,
  // 函数类型的表示方式
  show(id: number): void,
  filter?: (id: number) => void,
  info?: InfoFn,
  author?: User
}

const book: Book = {
  id: 1,
  name: "javascript",
  price: 100,
  show(id: number) { 
    console.log(id);
  },
  filter(id: number) { 
    console.log(id);
  },
  info(id: number, name?: string) { 
    return "hello";
  },
  author: {
    id: 1,
    name: "jack",
    age: 20
  }
}
// 交叉类型
// 交叉类型是将多个类型合并为一个类型
// 类型A & 类型B

type A = {
  id: number
  name: string
}

type B = {
  age: number
}

type A1 = {
  sex: "男" | "女"
  type: "Admin" | "Vip" | "Client"
}

type C = A & B & A1;
type D = A | B;
type E = string | number | boolean
// type F = string & number & boolean // never

const obj5: C = {
  id: 1,
  name: "jack",
  age: 20,
  sex: "男",
  type: "Admin"
}

const obj6: D = {
  id: 1,
  name: "jack",
  age: 20,
}

// 类型断言
// 简单来说，TS会根据上下文进行推测，但是有时候我们可以人为干涉，确定某一个类型
// 类型断言就是告诉TS编译器，”我知道我在做什么，这里没有类型安全问题，我自己来掌控“
// 允许我们使用更宽松的方式处理类型问题
// 语法：1、值 as 类型 或 2、<类型>值
let someValue: any = "this is a string";
// 建议使用as的方式，因为react中的jsx语法和<类型>值方式会产生歧义
let strLength1 = (someValue as string).length;
let strLength2 = (<string>someValue).length;

// 非空断言
// 当你确定某个值不是null或者undefined时候，可以直接使用非空断言
// 语法：值!
let maybeString: string | undefined = "hello";
let defineString = maybeString!;

function getRandom(length?:number) { 
  if (!length || length <= 0) { 
    return undefined
  }

  return Math.random().toString(36).slice(-length);
}
let s = getRandom(6);
(s as string).charAt(0)
// 非空断言
s!.charAt(0);

type Box = {
  id: number
  name: string
}

function getBox(): Box | undefined { 
  if (Math.random() > 0.5) {
    return {
      id: 1,
      name: "box"
    } as Box
  }
  return undefined
}

function createProduction(box: Box) {
  // todos...
}
createProduction(getBox()!)
createProduction(getBox() as Box)

// 比如常见的DOM操作
// const inputDom = document.querySelector("input");
// inputDom!.addEventListener("change", e => { 
//   console.log((e.target as HTMLInputElement).value);
// });

// 可选链操作符 ES2020 node14+
// 语法：对象?.属性名
interface Address{
  city?: string
  street?: string
}

interface Student{
  name: string
  address?: Address
}

const student: Student = {
  name: "jack",
  address: {
    city: "成都",
    // street:"人民大街"
  }
}
let street = student.address?.street;
console.log(street)
